import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, tap, finalize } from 'rxjs/operators';

import { PagedQueryResult, PagedQueryParam } from '../models';
import { Utils } from '../utils/utils';

declare var isRunning: number; //用于记录有多少ajax请求正在运行
declare var lastActiveTime: Date; // 用于记录最后活动时间

/**
 * 本是Service的基类，所有Service应该继承本类。本类主要有如下：<br>
 * <ul>
 * <li>统一的http ajax请求，如doGet, doPost等</li>
 * <li>http ajax请求时，{link BaseComponent}配合统一显示等待遮罩</li>
 * <li>doGet时，自动把对象转成请求参数，配合后台直接转换成对象</li>
 * <li>发送请求时，自动延长当前会话时间计数器，以便页面能在正确的时间后自动登出</li>
 * <li>对取得结果进行处理，把以Date, Time, Timestamp结尾的属性转为Date型</li>
 * </ul>
 * @example
 * 
 * import {HttpClient} from '@angular/common/http';
 * import {BaseService } from 'iisp-ng';
 * export class XXXService extends BaseService{
 *  constructor(http: HttpClient) { super(http); }
 *  ...
 *  }
 * 
 */
export abstract class BaseService {
	protected headers = new HttpHeaders({ 'Content-Type': 'application/json' });

	constructor(private http: HttpClient) {
		lastActiveTime = new Date();
	}
	/**
	 * 分页查询
	 *  @param url URL地址
	 *  @param queryParam 用于查询的参数
     *  @param waitLoading 是否要显示等待加载遮罩
	 */
	protected searchPage(url: string, queryParam: PagedQueryParam, waitLoading: boolean = true): Observable<PagedQueryResult<any>> {
		if (waitLoading) {
			isRunning++;
		}

		if (queryParam.pageNumber === undefined) {
			queryParam.pageNumber = 0;
		}
		if (!queryParam.pageSize) {
			queryParam.pageSize = 10;
		}
		if (url.indexOf("?") < 0) {
			url += "?x=1";
		}
		url += this.toQueryString(queryParam, "");
		return this.http.get<PagedQueryResult<any>>(url).pipe(
			tap(response => {
				let rst: PagedQueryResult<any> = response;
				this.processDate(rst.datas);
			}),
			catchError(err => this.handleError(err, waitLoading)),
            finalize(()=> {lastActiveTime = new Date(); if (waitLoading) isRunning--;}));
	}
	
    /**
     * 把输入的Object转换成请求参数串。
     * 如 obj = {a:1, b:[{i:3, k:4},{i:5}]，则返回的请求串为：
     * "&a=1&b[0].i=3&b[0].k=4&b[1].i=5"
     * 
     */
	toQueryString(obj: any, propPrefix: string): string {
		let url = "";
		let preParam = propPrefix;
		if (preParam) {
			preParam += ".";
		}
		if (obj == undefined) {
			return url;
		}
		if (typeof obj == 'object') {
			if (obj instanceof Array) {
				for (let i = 0; i < obj.length; i++) {
					//if (typeof (obj[i]) == 'object') {
					url = url + this.toQueryString(obj[i], propPrefix + "[" + i + "]");
					//} else {
					//url = url + "&" + propPrefix + "[" + i + "]" + "=" + this.formatValue(obj[i]);
					//}
				}
			} else {
				for (let prop in obj) {
					if (prop != "constructor") {
						let pv = obj[prop];
						if (pv != null && pv != undefined) {
							if (typeof pv == 'function') continue;
							//if (typeof pv == 'object') {
							url = url + this.toQueryString(pv, preParam + prop);
							//} else {
							//url = url + "&" + preParam + prop + "=" + this.formatValue(obj[prop]);
							//}
						}
					}
				}
			}
		} else {
			url = url + "&" + propPrefix + "=" + this.formatValue(obj);
		}
		return url;
	}
	/**
	 * 把值变成可以用于请求参数的串
	 * @param val 要变成串的值
	 */
	private formatValue(val: any) {
		if (val instanceof Date) {
			return val.getTime();
		}
		return encodeURI(val);
	}
    /**
     * GET请求
     * @param url URL地址
	 * @param data用于查询的参数
	 * @param waitLoading 是否要显示等待加载遮罩（缺省true）
     */
	protected doGet(url: string, data?: any, waitLoading: boolean = true): Observable<any> {
		if (data) {
			if (url.indexOf("?") < 0) {
				url += "?a=1";
			}
		}
		url += this.toQueryString(data, "");
		if (waitLoading) {
			isRunning++;
		}
		return this.http.get(url)
			.pipe(
			tap(resp => {
				this.processDate(resp);
			}),
			catchError(err => this.handleError(err, waitLoading)),
			finalize(()=> {lastActiveTime = new Date(); if (waitLoading) isRunning--;}));
	}

    /**
     * PUT请求
     * @param url URL地址,
     * @param data 发送到服务器的数据
     * @param waitLoading 是否要显示等待加载遮罩（缺省true）
     */
	protected doPut(url: string, data: any, waitLoading: boolean = true): Observable<any> {
		if (waitLoading) {
			isRunning++;
		}

		return this.http
			.put(url, data, { headers: this.headers })
			.pipe(
			tap(resp => {
				this.processDate(resp);
			}),
			catchError(err => this.handleError(err, waitLoading)),
            finalize(()=> {lastActiveTime = new Date(); if (waitLoading) isRunning--;}));
	}
    /**
     * POST请求
     * @param url URL地址
	 * @param data 要post的数据
	 * @param waitLoading 是否要显示等待加载遮罩（缺省true）
     *
     */
	protected doPost(url: string, data: any, waitLoading: boolean = true): Observable<any> {
		if (waitLoading) {
			isRunning++;
		}

		return this.http
			.post(url, JSON.stringify(data), { headers: this.headers })
			.pipe(
			tap(response => {
				this.processDate(response);
				return response;
			}),
			catchError(err => this.handleError(err, waitLoading)),
            finalize(()=> {lastActiveTime = new Date(); if (waitLoading) isRunning--;}));
	}
    /**
     * DELETE请求
     * @param url URL地址, 
     * @param waitLoading 是否要显示等待加载遮罩（缺省true）
     */
	protected doDelete(url: string, waitLoading: boolean = true): Observable<any> {
		if (waitLoading) {
			isRunning++;
		}
		return this.http.delete(url, { headers: this.headers })
			.pipe(
			tap(response => this.processResponse(response, waitLoading)),
			catchError(err => this.handleError(err, waitLoading)),
            finalize(()=> {lastActiveTime = new Date(); if (waitLoading) isRunning--;}));
	}
	/**
	 * 
	 * @param response
	 * @param waitLoading
	 */
	protected processResponse(response: any, waitLoading: boolean) {
		return this.processDate(response);
	}
	protected processDate(rst: any): any {
		if (rst) {
			if (Array.isArray(rst)) {
				for (let r of rst) {
					this.processDate(r);
				}
			} else {
				for (let p in rst) {
					if (typeof (rst[p]) == "object") {
						this.processDate(rst[p]);
					} else if (p.endsWith("Date") || p.endsWith("Time") || p.endsWith("Timestamp")) {
						if (typeof (rst[p]) == "number") {
							rst[p] = new Date(rst[p]);
						} else if (rst[p].length == 28 && rst[p].indexOf("T") == 10) {//"2017-11-29T12:18:54.921+0000"
							rst[p] = new Date(rst[p]);
						}
					}
				}
			}
		}
		return rst;
	}
	protected handleError(error: any, waitLoading: boolean): Observable<any> {
		let msg = Utils.resolveJsonErrorMessage(error);
		return throwError(msg);
	}
}